#pragma once

#include <map>
#include <vector>
#include <regex>
#include <iostream>

class Compiler
{
public:
	static double parse(const std::string str, bool *ok = nullptr);

private:
	struct Error { std::string what; };
	struct Input { int id; std::string data; };
	struct Element { int id; bool isSymbol; };

	class Variant
    {
    public:
        enum class Type { Undefined, Value, Oper };
        enum class Oper { 
			Sum = 10, Sub = 11, Mul = 12, Div = 13,
			LB = 14, RB = 15
		};

        Variant() = default;
        Variant(double value);
        Variant(Oper oper);
        Variant(const Variant &other) { copyFrom(other); }
        Variant(Variant &&other)  { copyFrom(other); }
		Variant& operator=(const Variant&other)  { copyFrom(other); return *this; }
        Variant& operator=(Variant&&other)  { copyFrom(other); return *this; }
        ~Variant();

        double toValue() const;
        Oper toOper() const;
		Type getType() const { return type; }

    private:
        void copyFrom(const Variant &other);

        Type type = Type::Undefined;
        void *pData;
    };

	class InputList
	{
	public:
		const Input& current() {
			if(index >= (int)vec.size()) throw Error{ "Compiler Error: InputList::current()" };
			return vec[(size_t)index];
		}
		void toNext() { index++; }

		std::vector<Input> vec;
		int index = 0;
	};

	class Node;
    class Helper
    {
    public:
        Helper(Node &parent, int index) : parent(parent), index(index) {}
        Node &parent;
        int index;
    };

	class Node
	{
	public:
		Node() = default;
		Node(int id, bool isSymbol);
		Node(const Node&) = delete;
		Node &operator=(const Node&) = delete;
		~Node();

		bool analysis(InputList &il, Helper *helper = nullptr);

		enum class DataType { Val, Inh, Syn };
		std::map<DataType, Variant> data;
		Element element;
		Node *childs = nullptr;
		int childCount = -1;
	};

	static std::regex regexToken;
	static std::map<char, int> mapOper;

	typedef double(*OperFn)(double a, double b);
	static std::map<int, OperFn> mapOperFn;

	static bool isNonterminal(int id) { return id >= 0 && id <= 8; }
	static bool isTerminal(int id) { return id > 8 && id <= 17; }

	static std::vector<std::vector<Element>> prods;
	static std::vector<std::map<int, const std::vector<Element>&>> select;

	typedef void(*ActionFn)(Helper *helper);
	static std::vector<ActionFn> actions;

	static void checkHelper(Helper *helper) { if(!helper) throw Error{ "Compiler Error: Compiler::checkHelper()" }; }
	static void checkContains(Helper *helper, int index) { if(index < 0 || index > helper->parent.childCount) throw Error{ "Compiler Error: Compiler::checkContains()" }; }
	static void fnActExpr1(Helper *helper) {
		checkHelper(helper);
		int indexLeft = helper->index - 1;
		checkContains(helper, indexLeft);
		int indexRight = helper->index + 1;
		checkContains(helper, indexRight);
		helper->parent.childs[indexRight].data[Node::DataType::Inh] = helper->parent.childs[indexLeft].data[Node::DataType::Val];
	}
	static void fnActExpr2(Helper *helper) {
		checkHelper(helper);
		int index = helper->index - 1;
		checkContains(helper, index);
		helper->parent.data[Node::DataType::Val] = helper->parent.childs[index].data[Node::DataType::Syn];
	}
	static void fnActExpr3(Helper *helper) {
		checkHelper(helper);
		int index1 = helper->index - 2;
		checkContains(helper, index1);
		int index2 = helper->index - 1;
		int index3 = helper->index + 1;
		checkContains(helper, index3);
		auto iter = mapOperFn.find((int)helper->parent.childs[index1].data[Node::DataType::Syn].toOper());
		if(iter == mapOperFn.end())
			throw Error{ "CompilerError: Compiler::fnActExpr3()" };
		double result = (*iter).second(helper->parent.data[Node::DataType::Inh].toValue(), helper->parent.childs[index2].data[Node::DataType::Val].toValue());
		helper->parent.childs[index3].data[Node::DataType::Inh] = result;
	}
	static void fnActExpr4(Helper *helper) {
		checkHelper(helper);
		int index = helper->index - 1;
		checkContains(helper, index);
		helper->parent.data[Node::DataType::Syn] = helper->parent.childs[index].data[Node::DataType::Syn];
	}
	static void fnActExpr5(Helper *helper) {
		checkHelper(helper);
		helper->parent.data[Node::DataType::Syn] = helper->parent.data[Node::DataType::Inh];
	}
	static void fnActSendOper(Helper *helper) {
		checkHelper(helper);
		int index = helper->index - 1;
		checkContains(helper, index);
		helper->parent.data[Node::DataType::Syn] = helper->parent.childs[index].data[Node::DataType::Syn];
	}
	static void fnActSendVal(Helper *helper) {
		checkHelper(helper);
		int index = helper->index - 1;
		checkContains(helper, index);
		helper->parent.data[Node::DataType::Val] = helper->parent.childs[index].data[Node::DataType::Val];
	}
	static void fnActSendNegativeVal(Helper *helper) {
		checkHelper(helper);
		int index = helper->index - 1;
		checkContains(helper, index);
		helper->parent.data[Node::DataType::Val] = -helper->parent.childs[index].data[Node::DataType::Val].toValue();
	}
};

